﻿#include "mppencoder.h"

MppEncoder::MppEncoder(int w, int h, int fps)
{
    this->width = w;
    this->height = h;
    this->fps = fps;

    init();
}

void MppEncoder::init()
{
    MPP_RET ret = MPP_OK;
    MppPollType timeout = MPP_POLL_BLOCK;

    m_mppEncData.width      = width;
    m_mppEncData.height     = height;
    m_mppEncData.hor_stride = MPP_ALIGN(width, 16);
    m_mppEncData.ver_stride = height;
    m_mppEncData.fmt        = MPP_FMT_YUV420P;
    m_mppEncData.frame_size = MPP_ALIGN(m_mppEncData.hor_stride, 64) * MPP_ALIGN(m_mppEncData.ver_stride, 64) * 3 / 2;

    m_mppEncData.type = MPP_VIDEO_CodingAVC;
    ret = mpp_buffer_group_get_internal(&(m_mppEncData.buf_grp), MPP_BUFFER_TYPE_DRM);
    if (ret){
        qDebug() << "failed to get buffer group internal ret "  <<  ret;
        goto MPP_INIT_OUT;
    }

    ret = mpp_buffer_get(m_mppEncData.buf_grp, &(m_mppEncData.frm_buf), m_mppEncData.frame_size);
    if (ret) {
        qDebug() << "failed to get buffer for input frame ret "  <<  ret;
        goto MPP_INIT_OUT;
    }

    ret = mpp_buffer_get(m_mppEncData.buf_grp, &(m_mppEncData.pkt_buf), m_mppEncData.frame_size);
    if (ret) {
        qDebug() << "failed to get pkt buffer internal ret "  <<  ret;
        goto MPP_INIT_OUT;
    }
    //创建MPP解码器上下文,初始化mpi
    ret = mpp_create(&(m_mppEncData.ctx), &(m_mppEncData.mpi));
    if (ret) {
        qDebug() << "mpp_create failed ret " << ret;
        goto MPP_INIT_OUT;
    }

    ret = m_mppEncData.mpi->control(m_mppEncData.ctx, MPP_SET_OUTPUT_TIMEOUT, &timeout);
    if (MPP_OK != ret) {
        qDebug() << "mpp control timeout ret " << ret;
        goto MPP_INIT_OUT;
    }

    ret = mpp_init(m_mppEncData.ctx, MPP_CTX_ENC, m_mppEncData.type);
    if (ret) {
        qDebug() << "mpp_init failed ret " << ret;
        goto MPP_INIT_OUT;
    }
    //帧率
    m_mppEncData.fps = fps;
    m_mppEncData.gop = m_mppEncData.fps * 2;
    //像素质量
    m_mppEncData.bps = (m_mppEncData.width * m_mppEncData.height) / 8 * m_mppEncData.fps;

    m_mppEncData.prep_cfg.change        = MPP_ENC_PREP_CFG_CHANGE_INPUT | MPP_ENC_PREP_CFG_CHANGE_ROTATION | MPP_ENC_PREP_CFG_CHANGE_FORMAT;
    m_mppEncData.prep_cfg.width         = m_mppEncData.width;
    m_mppEncData.prep_cfg.height        = m_mppEncData.height;
    m_mppEncData.prep_cfg.hor_stride    = m_mppEncData.hor_stride;
    m_mppEncData.prep_cfg.ver_stride    = m_mppEncData.ver_stride;
    m_mppEncData.prep_cfg.format        = m_mppEncData.fmt;
    m_mppEncData.prep_cfg.rotation      = MPP_ENC_ROT_0;
    ret = m_mppEncData.mpi->control(m_mppEncData.ctx, MPP_ENC_SET_PREP_CFG, &m_mppEncData.prep_cfg);
    if (ret) {
        qDebug() << "mpi control enc set prep cfg failed ret " << ret;
        goto MPP_INIT_OUT;
    }

    m_mppEncData.rc_cfg.change  = MPP_ENC_RC_CFG_CHANGE_ALL;
    m_mppEncData.rc_cfg.rc_mode = MPP_ENC_RC_MODE_CBR;
    m_mppEncData.rc_cfg.quality = MPP_ENC_RC_QUALITY_MEDIUM;

    if (m_mppEncData.rc_cfg.rc_mode == MPP_ENC_RC_MODE_CBR)
    {
        /* constant bitrate has very small bps range of 1/16 bps */
        m_mppEncData.rc_cfg.bps_target   = m_mppEncData.bps;
        m_mppEncData.rc_cfg.bps_max      = m_mppEncData.bps * 17 / 16;
        m_mppEncData.rc_cfg.bps_min      = m_mppEncData.bps * 15 / 16;
    }
    else if (m_mppEncData.rc_cfg.rc_mode ==  MPP_ENC_RC_MODE_VBR)
    {
        if (m_mppEncData.rc_cfg.quality == MPP_ENC_RC_QUALITY_CQP)
        {
            /* constant QP does not have bps */
            m_mppEncData.rc_cfg.bps_target   = -1;
            m_mppEncData.rc_cfg.bps_max      = -1;
            m_mppEncData.rc_cfg.bps_min      = -1;
        } else {
            /* variable bitrate has large bps range */
            m_mppEncData.rc_cfg.bps_target   = m_mppEncData.bps;
            m_mppEncData.rc_cfg.bps_max      = m_mppEncData.bps * 17 / 16;
            m_mppEncData.rc_cfg.bps_min      = m_mppEncData.bps * 1 / 16;
        }
    }

    /* fix input / output frame rate */
    m_mppEncData.rc_cfg.fps_in_flex      = 0;
    m_mppEncData.rc_cfg.fps_in_num       = m_mppEncData.fps;
    m_mppEncData.rc_cfg.fps_in_denorm    = 1;
    m_mppEncData.rc_cfg.fps_out_flex     = 0;
    m_mppEncData.rc_cfg.fps_out_num      = m_mppEncData.fps;
    m_mppEncData.rc_cfg.fps_out_denorm   = 1;

    m_mppEncData.rc_cfg.gop              = m_mppEncData.gop;
    m_mppEncData.rc_cfg.skip_cnt         = 0;
    m_mppEncData.rc_cfg.drop_mode        = MPP_ENC_RC_DROP_FRM_DISABLED;
    m_mppEncData.rc_cfg.drop_threshold   = 20;
    m_mppEncData.rc_cfg.drop_gap         = 1;

    ret = m_mppEncData.mpi->control(m_mppEncData.ctx, MPP_ENC_SET_RC_CFG, &m_mppEncData.rc_cfg);
    if (ret) {
        qDebug() << "mpi control enc set rc cfg failed ret " << ret;
        goto MPP_INIT_OUT;
    }

    m_mppEncData.codec_cfg.coding = m_mppEncData.type;
    switch (m_mppEncData.codec_cfg.coding)
    {
    case MPP_VIDEO_CodingAVC :
    {
        m_mppEncData.codec_cfg.h264.change = MPP_ENC_H264_CFG_CHANGE_PROFILE |
                MPP_ENC_H264_CFG_CHANGE_ENTROPY |
                MPP_ENC_H264_CFG_CHANGE_TRANS_8x8;
        /*
         * H.264 profile_idc parameter
         * 66  - Baseline profile
         * 77  - Main profile
         * 100 - High profile
         */
        m_mppEncData.codec_cfg.h264.profile  = 100;
        /*
         * H.264 level_idc parameter
         * 10 / 11 / 12 / 13    - qcif@15fps / cif@7.5fps / cif@15fps / cif@30fps
         * 20 / 21 / 22         - cif@30fps / half-D1@@25fps / D1@12.5fps
         * 30 / 31 / 32         - D1@25fps / 720p@30fps / 720p@60fps
         * 40 / 41 / 42         - 1080p@30fps / 1080p@30fps / 1080p@60fps
         * 50 / 51 / 52         - 4K@30fps
         */
        if(width==1280 && height==720){
            m_mppEncData.codec_cfg.h264.level    = 31;
        }else if(width==1920 && height==1080) {
            m_mppEncData.codec_cfg.h264.level    = 41;
        }

        m_mppEncData.codec_cfg.h264.entropy_coding_mode  = 1;
        m_mppEncData.codec_cfg.h264.cabac_init_idc       = 0;
        m_mppEncData.codec_cfg.h264.transform8x8_mode    = 1;

        m_mppEncData.codec_cfg.h264.qp_init              = 26;
        m_mppEncData.codec_cfg.h264.qp_max               = 51;
        m_mppEncData.codec_cfg.h264.qp_min               = 10;
        m_mppEncData.codec_cfg.h264.qp_max_i             = 46;
        m_mppEncData.codec_cfg.h264.qp_min_i             = 24;
    }break;
    case MPP_VIDEO_CodingMJPEG :
    {
        m_mppEncData.codec_cfg.jpeg.change  = MPP_ENC_JPEG_CFG_CHANGE_QP;
        m_mppEncData.codec_cfg.jpeg.quant   = 10;
    } break;
    case MPP_VIDEO_CodingVP8 :
    case MPP_VIDEO_CodingHEVC :
    default :
    {
        qDebug() << "support encoder coding type " << m_mppEncData.codec_cfg.coding;
    }break;
    }//switch (m_mppEncData.codec_cfg.coding)

    ret = m_mppEncData.mpi->control(m_mppEncData.ctx, MPP_ENC_SET_CODEC_CFG, &m_mppEncData.codec_cfg);
    if (ret) {
       qDebug() << "mpi control enc set codec cfg failed ret " << ret;
        goto MPP_INIT_OUT;
    }

    /* optional */
    m_mppEncData.sei_mode = MPP_ENC_SEI_MODE_ONE_FRAME;
    ret = m_mppEncData.mpi->control(m_mppEncData.ctx, MPP_ENC_SET_SEI_CFG, &m_mppEncData.sei_mode);
    if (ret) {
        qDebug() << "mpi control enc set sei cfg failed ret " << ret;
        goto MPP_INIT_OUT;
    }

    if (m_mppEncData.type == MPP_VIDEO_CodingAVC || m_mppEncData.type == MPP_VIDEO_CodingHEVC)
    {
        MppPacket packet = nullptr;

        /*
         * Can use packet with normal malloc buffer as input not pkt_buf.
         * Please refer to vpu_api_legacy.cpp for normal buffer case.
         * Using pkt_buf buffer here is just for simplifing demo.
         */
        mpp_packet_init_with_buffer(&packet, m_mppEncData.pkt_buf);
        /* NOTE: It is important to clear output packet length!! */
        mpp_packet_set_length(packet, 0);

        ret = m_mppEncData.mpi->control(m_mppEncData.ctx, MPP_ENC_GET_EXTRA_INFO, &packet);
        if (ret) {
            qDebug() << "mpi control enc get extra info failed";
            goto MPP_INIT_OUT;
        }

        /* get and write sps/pps for H.264 */
        if (packet)
        {
            void *ptr   = mpp_packet_get_pos(packet);
            size_t len  = mpp_packet_get_length(packet);

            m_frameHeader.append((const char *)ptr, len);

            mpp_packet_deinit(&packet);
            packet = nullptr;
        }
    }

    return ;

MPP_INIT_OUT:
    if (m_mppEncData.ctx) {
        mpp_destroy(m_mppEncData.ctx);
        m_mppEncData.ctx = nullptr;
    }

    if (m_mppEncData.frm_buf) {
        mpp_buffer_put(m_mppEncData.frm_buf);
        m_mppEncData.frm_buf = nullptr;
    }

    if (m_mppEncData.pkt_buf) {
        mpp_buffer_put(m_mppEncData.pkt_buf);
        m_mppEncData.pkt_buf = nullptr;
    }

    if (m_mppEncData.buf_grp) {
        mpp_buffer_group_put(m_mppEncData.buf_grp);
        m_mppEncData.buf_grp = nullptr;
    }

    qDebug() << "init mpp failed!";
    return ;
}

MPP_RET MppEncoder::encode(QByteArray & pArray)
{
    MPP_RET ret = MPP_OK;
    MppFrame frame = nullptr;
    MppPacket packet = nullptr;

    ret = mpp_frame_init(&frame);
    if (ret) {
        qDebug() << "mpp_frame_init failed";
        return MPP_NOK;
    }

    mpp_frame_set_width(frame, m_mppEncData.width);
    mpp_frame_set_height(frame, m_mppEncData.height);
    mpp_frame_set_hor_stride(frame, m_mppEncData.hor_stride);
    mpp_frame_set_ver_stride(frame, m_mppEncData.ver_stride);
    mpp_frame_set_fmt(frame, m_mppEncData.fmt);
    mpp_frame_set_buffer(frame, m_mppEncData.frm_buf);
    mpp_frame_set_eos(frame, m_mppEncData.frm_eos);

    //meta = mpp_frame_get_meta(frame);
    mpp_packet_init_with_buffer(&packet, m_mppEncData.pkt_buf);
    /* NOTE: It is important to clear output packet length!! */
    mpp_packet_set_length(packet, 0);
    //mpp_meta_set_packet(meta, KEY_OUTPUT_PACKET, packet);

    ret = m_mppEncData.mpi->encode_put_frame(m_mppEncData.ctx, frame);
    if (ret)
    {
        qDebug() << "mpp encode put frame failed";
        return MPP_NOK;
    }
    mpp_frame_deinit(&frame);
    ret = m_mppEncData.mpi->encode_get_packet(m_mppEncData.ctx, &packet);
    if (ret)
    {
        qDebug() << "mpp encode get packet failed";
        return MPP_NOK;
    }
    if(packet)
    {
        // write packet to file here
        void *ptr = mpp_packet_get_pos(packet);
        size_t pkt_len = mpp_packet_get_length(packet);
        m_mppEncData.pkt_eos = mpp_packet_get_eos(packet);

        //数据保存
        pArray.append((char *)ptr, pkt_len);

        mpp_packet_deinit(&packet);
        m_mppEncData.frame_count++;
        m_mppEncData.stream_size += pkt_len;

        if (m_mppEncData.pkt_eos) {
            qDebug() << "found last packet";
        }
    }

    if (m_mppEncData.frm_eos && m_mppEncData.pkt_eos) {
        return MPP_NOK;
    }

    return MPP_OK;
}

void MppEncoder::deinit()
{
    MPP_RET ret = MPP_OK;
    ret = m_mppEncData.mpi->reset(m_mppEncData.ctx);
    if (ret) {
        qDebug() << "mpi->reset failed";
        return ;
    }
    if (m_mppEncData.ctx) {
        mpp_destroy(m_mppEncData.ctx);
        m_mppEncData.ctx = nullptr;
    }
    if (m_mppEncData.frm_buf) {
        mpp_buffer_put(m_mppEncData.frm_buf);
        m_mppEncData.frm_buf = nullptr;
    }
    if (m_mppEncData.pkt_buf) {
        mpp_buffer_put(m_mppEncData.pkt_buf);
        m_mppEncData.pkt_buf = nullptr;
    }
    if (m_mppEncData.buf_grp) {
        mpp_buffer_group_put(m_mppEncData.buf_grp);
        m_mppEncData.buf_grp = nullptr;
    }
}
